package lpg.tool.optionpg;

import java.io.File;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
import lpg.LpgException;
import lpg.OptionEnum;
import lpg.util.DispatchWriter;
import lpg.util.Utility;

class Control
{
    Control(File parserFile)
    {
        this.parserFile = parserFile;
    }

    void process()
    {
        for (OptionEnum option : OptionEnum.values())
        {
            optionNumber++;
            processOption(optionMap, option);
        }

        for (Node node : optionMap.values())
        {
            processNode(node);
        }

        // Generate parser file
        DispatchWriter out = null;
        try
        {
            out = new DispatchWriter();
            Generator generator = new Generator(optionMap, parserFile, out);
            generator.run();

            out.close();
            out = null;
        }
        finally
        {
            Utility.close(out);
        }

        System.out.println("Processed " + optionNumber + " option(s)");
    }

    private void processOption(Map<Character, Node> nodeMap, OptionEnum option)
    {
        String name = option.name();

        // Check option name
        int index = -1;
        for (int i = 0, n = name.length(); i < n; i++)
        {
            char c = name.charAt(i);
            if (c == '_')
            {
                if (index != -1 || i == 0 || i == n - 1)
                {
                    throw new LpgException("\"" + name + "\" is an invalid option name.");
                }
                index = i;
            }
            else if (c >= 128)
            {
                throw new LpgException("\"" + name + "\" is an invalid option name.");
            }
        }

        if (name.startsWith("NO"))
        {
            throw new LpgException("\"" + name + "\" is an invalid option name.");
        }
        processOption(nodeMap, name, option);
        if (index > 0)
        {
            String subName1 = name.substring(0, index);
            String subName2 = name.substring(index + 1);

            String name1 = subName1.substring(0, 1) + subName2.substring(0, 1);
            if (name1.equals("NO"))
            {
                throw new LpgException("\"" + name + "\" is an invalid option name.");
            }
            processOption(nodeMap, name1, option);

            String name2 = subName1 + subName2;
            processOption(nodeMap, name2, option);
        }
    }

    private void processOption(Map<Character, Node> nodeMap, String name, OptionEnum option)
    {
        int priority = option.getPriority();
        for (int i = 0, n = name.length(); i < n; i++)
        {
            char c = name.charAt(i);
            Node node = nodeMap.get(c);
            if (node == null)
            {
                node = new Node(String.valueOf(c), name.substring(0, i + 1));
                nodeMap.put(c, node);
            }

            int optionPriority = node.optionSet.isEmpty() ?
                    priority : node.optionSet.iterator().next().getPriority();
            if (priority == optionPriority)
            {
                node.optionSet.add(option);
            }
            else if (priority < optionPriority)
            {
                node.optionSet.clear();
                node.optionSet.add(option);
            }

            if (i < n - 1 && node.children == null)
            {
                node.children = new TreeMap<Character, Node>();
            }
            nodeMap = node.children;
        }
    }

    // Merge nodes and count options on nodes
    private void processNode(Node node)
    {
        Node pNode = node;
        while (!pNode.isLeaf() && pNode.children.size() == 1)
        {
            pNode = pNode.children.values().iterator().next();
            if (node.optionSet.equals(pNode.optionSet))
            {
                node.mergeChild(pNode);
            }
        }
        node.countOptions();

        if (node.isLeaf())
        {
            return;
        }

        for (Node childNode : node.children.values())
        {
            processNode(childNode);
        }

        Map<EnumSet<OptionEnum>, Node> leafNodes = new HashMap<EnumSet<OptionEnum>, Node>();
        for (Iterator<Node> it = node.children.values().iterator(); it.hasNext();)
        {
            Node childNode = it.next();
            if (childNode.isLeaf())
            {
                Node leaf = leafNodes.get(childNode.optionSet);
                if (leaf == null)
                {
                    leafNodes.put(childNode.optionSet, childNode);
                }
                else
                {
                    leaf.mergeBrother(childNode);
                    it.remove();
                }
            }
        }

        if (node.children.size() == 1)
        {
            Node childNode = node.children.values().iterator().next();
            if (node.optionSet.equals(childNode.optionSet))
            {
                node.mergeChild(childNode);
            }
        }
    }

    static String toString(EnumSet<OptionEnum> optionSet)
    {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        for (OptionEnum option : optionSet)
        {
            if (first)
            {
                first = false;
            }
            else
            {
                buf.append(", ");
            }
            buf.append(option);
        }
        return new String(buf);
    }

    private int optionNumber = 0;
    private final Map<Character, Node> optionMap = new TreeMap<Character, Node>();
    private final File parserFile;

}
